package io.gitee.verify.base;

import io.gitee.define.entity.Verify;

import java.awt.*;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.QuadCurve2D;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Base64;
import java.util.Objects;

/**
 * 验证码抽象类
 *
 * @author Cikaros
 * @date 2021/3/23
 */
public abstract class Captcha extends Randoms {

    /**
     * 常用颜色
     */
    protected static final int[][] COLOR = {
            {0, 135, 255}, {51, 153, 51}, {255, 102, 102}, {255, 153, 0},
            {153, 102, 0}, {153, 102, 153}, {51, 153, 153}, {102, 102, 255},
            {0, 102, 204}, {204, 51, 51}, {0, 153, 204}, {0, 51, 102}};

    public enum VerifyType {
        /**
         * 默认（数字字母混合）
         */
        DEFAULT,
        /**
         * 纯数字
         */
        ONLY_DIGIT,
        /**
         * 纯字母
         */
        ONLY_LITTER,
        /**
         * 大写字母
         */
        ONLY_UPPER,
        /**
         * 纯小写字母
         */
        ONLY_LOWER,
    }

    private Font font = null; // 验证码的字体

    protected int len = 5; // 验证码随机字符长度

    protected int width = 130; // 验证码显示宽度

    protected int height = 48; // 验证码显示高度

    protected VerifyType charType = VerifyType.DEFAULT; // 验证码类型

    protected String chars = null; // 当前验证码

    /**
     * 生成随机验证码
     */
    protected void alphas() {
        char[] cs = new char[len];
        for (int i = 0; i < len; i++) {
            switch (charType) {
                case ONLY_DIGIT:
                    cs[i] = alpha(numMaxIndex);
                    break;
                case ONLY_LITTER:
                    cs[i] = alpha(charMinIndex, charMaxIndex);
                    break;
                case ONLY_UPPER:
                    cs[i] = alpha(upperMinIndex, upperMaxIndex);
                    break;
                case ONLY_LOWER:
                    cs[i] = alpha(lowerMinIndex, lowerMaxIndex);
                    break;
                default:
                    cs[i] = alpha();
            }
        }
        chars = new String(cs);
    }

    /**
     * 给定范围获得随机颜色
     *
     * @param fc 0-255
     * @param bc 0-255
     * @return 随机颜色
     */
    protected Color color(int fc, int bc) {
        if (fc > 255)
            fc = 255;
        if (bc > 255)
            bc = 255;
        int r = fc + num(bc - fc);
        int g = fc + num(bc - fc);
        int b = fc + num(bc - fc);
        return new Color(r, g, b);
    }

    /**
     * 获取随机常用颜色
     *
     * @return 随机颜色
     */
    protected Color color() {
        int[] color = COLOR[num(COLOR.length)];
        return new Color(color[0], color[1], color[2]);
    }

    /**
     * 验证码输出,抽象方法，由子类实现
     *
     * @param os 输出流
     * @return 是否成功
     */
    protected abstract boolean out(OutputStream os);

    /**
     * 输出base64编码
     *
     * @return base64编码字符串
     */
    protected abstract String toBase64();

    /**
     * 获取图片类型
     *
     * @return 图片格式，MIME类型
     */
    protected abstract String getContentType();

    /**
     * 生成验证码对象
     */
    public Verify build() {
        byte[] data = null;
        try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            this.out(out);
            data = out.toByteArray();
        } catch (IOException ignored) {
        }
        return new Verify(chars, getContentType(), data);
    }

    /**
     * 输出base64编码
     *
     * @param type 编码头
     * @return base64编码字符串
     */
    protected String toBase64(String type) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        out(outputStream);
        return type + Base64.getEncoder().encodeToString(outputStream.toByteArray());
    }

    /**
     * 获取当前的验证码
     *
     * @return 字符串
     */
    protected String text() {
        checkAlpha();
        return chars;
    }

    /**
     * 获取当前验证码的字符数组
     *
     * @return 字符数组
     */
    protected char[] textChar() {
        checkAlpha();
        return chars.toCharArray();
    }

    /**
     * 检查验证码是否生成，没有则立即生成
     */
    protected void checkAlpha() {
        if (chars == null) {
            alphas(); // 生成验证码
        }
    }

    /**
     * 随机画干扰线
     *
     * @param num 数量
     * @param g   Graphics2D
     */
    public void drawLine(int num, Graphics2D g) {
        drawLine(num, null, g);
    }

    /**
     * 随机画干扰线
     *
     * @param num   数量
     * @param color 颜色
     * @param g     Graphics2D
     */
    public void drawLine(int num, Color color, Graphics2D g) {
        for (int i = 0; i < num; i++) {
            g.setColor(color == null ? color() : color);
            int x1 = num(-10, width - 10);
            int y1 = num(5, height - 5);
            int x2 = num(10, width + 10);
            int y2 = num(2, height - 2);
            g.drawLine(x1, y1, x2, y2);
        }
    }

    /**
     * 随机画干扰圆
     *
     * @param num 数量
     * @param g   Graphics2D
     */
    protected void drawOval(int num, Graphics2D g) {
        drawOval(num, null, g);
    }

    /**
     * 随机画干扰圆
     *
     * @param num   数量
     * @param color 颜色
     * @param g     Graphics2D
     */
    protected void drawOval(int num, Color color, Graphics2D g) {
        for (int i = 0; i < num; i++) {
            g.setColor(color == null ? color() : color);
            int w = 5 + num(10);
            g.drawOval(num(width - 25), num(height - 15), w, w);
        }
    }

    /**
     * 随机画贝塞尔曲线
     *
     * @param num 数量
     * @param g   Graphics2D
     */
    protected void drawBesselLine(int num, Graphics2D g) {
        drawBesselLine(num, null, g);
    }

    /**
     * 随机画贝塞尔曲线
     *
     * @param num   数量
     * @param color 颜色
     * @param g     Graphics2D
     */
    protected void drawBesselLine(int num, Color color, Graphics2D g) {
        for (int i = 0; i < num; i++) {
            g.setColor(color == null ? color() : color);
            int x1 = 5, y1 = num(5, height / 2);
            int x2 = width - 5, y2 = num(height / 2, height - 5);
            int ctrlx = num(width / 4, width / 4 * 3), ctrly = num(5, height - 5);
            if (num(2) == 0) {
                int ty = y1;
                y1 = y2;
                y2 = ty;
            }
            if (num(2) == 0) { // 二阶贝塞尔曲线
                QuadCurve2D shape = new QuadCurve2D.Double();
                shape.setCurve(x1, y1, ctrlx, ctrly, x2, y2);
                g.draw(shape);
            } else { // 三阶贝塞尔曲线
                int ctrlx1 = num(width / 4, width / 4 * 3), ctrly1 = num(5, height - 5);
                CubicCurve2D shape = new CubicCurve2D.Double(x1, y1, ctrlx, ctrly, ctrlx1, ctrly1, x2, y2);
                g.draw(shape);
            }
        }
    }

    protected Font getFont() {
        if (font == null) {
            setFont(new Font("Arial", Font.BOLD, 32));
        }
        return font;
    }

    public void setFont(Font font) {
        this.font = font;
    }

    public void setFont(String font) throws IOException, FontFormatException {
        setFont(font, 32f);
    }

    public void setFont(String font, float size) throws IOException, FontFormatException {
        setFont(font, Font.BOLD, size);
    }

    public void setFont(String font, int style, float size) throws IOException, FontFormatException {
        this.font = Font.createFont(Font.TRUETYPE_FONT, Objects.requireNonNull(getClass().getResourceAsStream("/" + font)))
                .deriveFont(style, size);
    }

    protected int getLen() {
        return len;
    }

    public void setLen(int len) {
        this.len = len;
    }

    protected int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    protected int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    protected VerifyType getCharType() {
        return charType;
    }

    public void setCharType(VerifyType charType) {
        this.charType = charType;
    }

}
